Jx.declare('Jx.lib.Json', {

	$singleton: true,

	$extend: 'Jx.lib.String',

	$helpers: {

		'String': 'Jx.lib.String'

	},

	$requires: [

		'code.json.parser',

		'code.json.url.tokenizer'

	],

	parserClass: 'code.json.parser',

	urlTokenizerClass: 'code.json.url.tokenizer',

	parser: undefined,

	urlTokenizer: undefined,

	_decodedDateRe: undefined,



	constructor: function(){

		this.$super();

		this.initializeParser();

		this.initializeUrlTokenizer();

	},

	initializeParser: function(){

		var parser = Jx.instantiate(this.parserClass);

		this._decodedDateRe = parser.tokenizer.dateRe;

		this.parser = parser;

	},

	initializeUrlTokenizer: function(){

		this.urlTokenizer = Jx.instantiate(this.urlTokenizerClass);

	},

	onParse: function(operation, rule, lexeme, values){

		var prime = values[0],

			jsonValue = undefined;

		switch(rule){

			case 'Name':

				lexeme.jsonName = prime.value;

				break;

			case 'Value':

				//console.log('value: ', prime.value, 'production: ', prime.production);

					var value = prime.value;

					switch(prime.production){

						case 'string':

								jsonValue = value;

							break;

						case 'number':

								jsonValue = parseFloat(value, 10);

							break;

						case 'undefined':

								jsonValue = void(0);

							break;

						case 'null':

								jsonValue = null;

							break;

						case 'true':

								jsonValue = true;

							break;

						case 'false':

								jsonValue = false;

							break;

						case 'date':

								jsonValue = (value = value.match( this._decodedDateRe)) ?

									new Date(

										parseInt(value[1], 10),

										parseInt(value[2], 10),

										parseInt(value[3], 10),

										parseInt(value[4], 10),

										parseInt(value[5], 10),

										parseInt(value[6], 10),

										0

									)

									:

									new Date();

							break;

						case 'Function':
						case 'Object':
						case 'Array':

							jsonValue = values[0].jsonValue;

							break;

					}

					lexeme.jsonValue = jsonValue;

				break;

			case 'Object':

					lexeme.jsonValue = values[1].production == '}' ?

						{}

						:

						values[1].jsonValue;

				break;

			case 'ObjectPairs':

					var start = 0;

					if (prime.production == 'ObjectPairs'){

						start = 2;

						jsonValue = prime.jsonValue;

					} else {

						jsonValue = {};

					}

					lexeme.jsonValue = jsonValue;

					jsonValue[

						values[ start ].jsonName

					] = values[ start + 2 ].jsonValue;

				break;

			case 'Array':

					prime = values[1];

					lexeme.jsonValue = prime.production == 'ArrayItems' ?

						prime.jsonValue

						:

						[];

				break;

			case 'ArrayItems':

					var start = 0;

					if (prime.production == 'ArrayItems'){

						jsonValue = prime.jsonValue;

						start = 2;

					} else {

						jsonValue = [];

					}

					lexeme.jsonValue = jsonValue;

					jsonValue[ jsonValue.length ] = values[ start ].jsonValue;

				break;

			case 'FuncParams':

					var start = 0;

					if (prime.production == 'FuncParams'){

						jsonValue = prime.jsonValue;

						start = 2;

					} else {

						jsonValue = [];

					}

					lexeme.jsonValue = jsonValue;

					jsonValue[ jsonValue.length ] = values[ start ].value;

				break;

			case 'Function':

					var code = '',

						params = '';

					for(var production, part, parts = values.slice(2), l = parts.length; l--;){

						part = parts[l];

						production = part.production;

						if (production == 'FuncParams'){

							params = part.jsonValue.join(',');

						} else if (production == 'FuncBlock'){

							code = part.getCode();

						}

					}

					lexeme.jsonValue = new Function(params, code);

				break;

			case 'JSON':

				operation.result = values[0].jsonValue;

				break;

		}

	},

	encode: function(o){

		var value = '',

			quote = '"',

			signature = Object.prototype.toString.call(o),

			type = typeof o,

			S = this.$String;

		if (o === null){

			value = 'null';

		} else if (type == 'undefined'){

			value = 'undefined';

		} else if (signature == '[object String]'){

			value = quote + S.escape(o) + quote;


		} else if (signature == '[object Number]'){

			value = String(o);

		} else if (signature == '[object Date]'){

			value = quote

				+ S.pad(o.getUTCFullYear(), 4, '0') + '-'

				+ S.pad(o.getUTCMonth(), 2, '0') + '-'

				+ S.pad(o.getUTCDate(), 2, '0') + 'T'

				+ S.pad(o.getUTCHours(), 2, '0') + ':'

				+ S.pad(o.getUTCMinutes(), 2, '0') + ':'

				+ S.pad(o.getUTCSeconds(), 2, '0') + 'Z'

			+ quote;

		} else if (signature == '[object Array]'){

			for(var items = [], l = o.length; l--;){

				items[l] = this.encode(o[l]);

			}

			value = '[' + items.join(',') + ']';


		} else if (type == 'object'){

			var items = [];

			Jx.each(o, function(n, v, obj){

				var q = this.q;

				this.items[ this.n ++ ] = q+n+q+': '+this.me.encode(v);

			}, { me: this, items: items = [], q: quote, n: 0 });

			value = '{' + items.join(',') + '}';

		}

		return value || 'null';

	},

	decode: function(str){

		var operation = {

				result: undefined,

				me: this

			};

		this.parser.parse(str, function(production, lexeme){

			this.me.onParse( this, production, lexeme, lexeme.value );

		}, operation);

		return operation.result;

	},

	access: function(url, o){

		var tokenizer = this.urlTokenizer;

		if (Jx.isString(url, true) && Jx.isObject(o)){

			tokenizer.set(url);

			for(var name, value, token = tokenizer.next(), subject = o; token; token = tokenizer.next()){

				name = token.token;

				if (name == 'ident'){

					value = token.value;

					if (Jx.isObject(subject) && value in subject){

						subject = subject[value];

					} else {

						break;

					}

				} else if (name == '$'){

					return subject;

				}

			}

		}

		return void(0);

	},

	createAccessor: function(url){

		var tokenizer = this.urlTokenizer,

			S = this.$String;

		if (Jx.isString(url, true)){

			tokenizer.set(url);

			for(var name, value, l, blocks = 0, code = [], token = tokenizer.next(); token; token = tokenizer.next()){

				name = token.token;

				if (name == 'ident'){

					value = S.escape(token.value);

					l = code.length;

					blocks++;

					code[ l ] = 'if (J.isObject(o) && "'+value+'" in o){';

					code[ l+1 ] = 'o = o["'+value+'"];';

				} else if (name == '$' && (l = code.length)){

					//-- inject return value
					code[ l - 1 ] = 'return '+code[ l - 1 ];

					//-- compile
					return new Function('o',

						'var J=Jx;'

						+ code.join('')

						+ S.repeat('}', blocks)

						+ 'return void(0);'

					);

				}

			}


		}

		return void(0);

	}

});
